# 添加eason碰上怪物后game over
import pygame  # 导入pygame模块
import sys  # 导入sys模块
import random  # 导入random模块
from pygame.locals import *  # 单独从pygame.locals中调用方法，减少方向控制的代码量


class RunMap(pygame.sprite.Sprite):  # 创建滚动地图类

    def __init__(self, map_x, map_y):  # 初始化地图的坐标和图片参数
        self.x = map_x  # 定义map_x为类的x轴坐标
        self.y = map_y  # 定义map_y为类的y轴坐标
        self.image = pygame.image.load("image/bg1.png")  # 读取地图图片到类的image变量中

    def map_rolling(self):  # 定义地图滚动函数
        if self.x <= -1000:  # 如果地图的x坐标小于-1000
            self.x = 1000  # 地图的x坐标更新为1000
        else:  # 否则
            self.x -= 5  # 每次循环地图向左移动5像素

    def map_update(self):  # 定义更新地图函数
        screen.blit(self.image, (self.x, self.y))  # 将地图的图片和坐标位置更新到屏幕上


class MySprite(pygame.sprite.Sprite):  # 定义游戏精灵类

    def __init__(self, image_file, frame_number, axis_x, axis_y):
        # 初始化游戏精灵类，设置image_file、frame_number、axis_x、axis_y为类的参数
        pygame.sprite.Sprite.__init__(self)  # 初始化动画精灵
        self.mast_image = pygame.image.load(image_file)  # 获取图片
        self.rect = self.mast_image.get_rect()  # 获取矩形的坐标数据
        self.frame_rect = self.rect.copy()  # 拷贝一份self.rect的数据到self.frame_rect里面
        self.rect.x = axis_x  # axis_x作为矩形的x轴坐标
        self.rect.y = axis_y  # axis_y作为矩形的y轴坐标
        self.frame_rect.width /= frame_number
        # 拷贝的矩形的宽度等于精灵图片的宽度除以精灵的个数，这样算出一个矩形的宽度
        self.rect.width /= frame_number  # 单个矩形的宽度等于精灵图片的宽度除以精灵的个数。
        self.frame_number = frame_number  # 精灵序列图中精灵的个数
        self.image_number = 0  # 图片的计数变量

    def update(self):  # 定义滚动精灵函数
        if self.image_number < self.frame_number:  # 如果图片的计数变量小于精灵图片中的精灵个数
            self.frame_rect.x = (self.image_number % self.frame_number) * self.frame_rect.width
            # 用计数变量除以精灵个数取余的值乘以矩形的宽度作为单个精灵的x坐标值
            self.image_number += 1  # 图片计数变量加1
        else:  # 否则
            self.image_number = 0  # 图片计数变量归0
        self.image = self.mast_image.subsurface(self.frame_rect)
        # 单个精灵图像的坐标值等于获取到的矩形的坐标值，这样就可以切割出一个精灵图像


class Explosion(pygame.sprite.Sprite):  # 爆炸效果多行多列精灵序列图的用法
    def __init__(self, image_file, frame_number, row_number, axis_x, axis_y):
        '''初始化爆炸效果类,参数包括:图片文件、每行精灵数量、精灵图片的行数、坐标x，坐标y'''
        pygame.sprite.Sprite.__init__(self)  # 初始化动画精灵
        self.mast_image = pygame.image.load(image_file)  # 获取图片
        self.rect = self.mast_image.get_rect()  # 获取矩形的坐标数据
        self.frame_rect = self.rect.copy()  # 复制一个矩形的数据，以便做切割
        self.rect.x = axis_x  # axis_x作为矩形的x轴坐标
        self.rect.y = axis_y  # axis_y作为矩形的y轴坐标
        self.frame_rect.width /= frame_number
        # 矩形的宽度等于精灵图片的宽度除以每行精灵的个数，这样算出一个矩形的宽度
        self.frame_rect.height /= row_number
        # 矩形的高度等于精灵图片的高度除以精灵图片的行数，这样算出一个矩形的高度
        self.rect.width /= frame_number  # 单个矩形的宽度等于精灵图片的宽度除以精灵的个数
        self.frame_number = frame_number  # 精灵序列图中精灵的个数
        self.row_number = row_number  # 精灵序列图中精灵的行数
        self.image_number = 0  # 图片的计数变量
        self.explosion_switch = False  # 定义爆炸开关，初始化为关闭

    def update(self):  # 定义滚动爆炸函数
        if self.explosion_switch == True:  # 如果爆炸开关为开
            if self.image_number < self.frame_number * self.row_number:
                # 如果图片的计数变量小于精灵图片中的精灵个数（每行精灵个数乘以行数）
                self.frame_rect.x = (self.image_number % self.frame_number) * self.frame_rect.width
                # 用计数变量除以精灵个数取余的值乘以矩形的宽度作为单个精灵的x坐标值
                self.frame_rect.y = (self.image_number // self.row_number) * self.frame_rect.height
                # 用计数变量除以精灵个数取余的值乘以矩形的高度作为单个精灵的y坐标值
                self.image_number += 1  # 图片计数变量加1
            else:  # 否则
                self.image_number = 0  # 图片计数变量归0
                self.explosion_switch = False  # 爆炸效果关闭
            self.image = self.mast_image.subsurface(self.frame_rect)
            # 单个精灵图像的坐标值等于获取到的矩形的坐标值，这样就可以切割出一个精灵图像


class Button(object):  # 定义按钮类
    def __init__(self, upimage, downimage, position):
        '''初始化按钮类，参数包括正常状态图片、按下的图片和坐标元组'''
        self.image_up = pygame.image.load(upimage).convert_alpha()
        # 读取按钮正常图片为按钮类的抬起状态
        self.image_down = pygame.image.load(downimage).convert_alpha()
        # 读取按钮按下图片为按钮类的按下状态
        self.position = position  # 坐标参数为按钮类的参数
        self.game_start = False  # 设置游戏开始开关

    def is_over(self):  # 定义鼠标移动到按钮上的效果
        point_x, point_y = pygame.mouse.get_pos()  # 获取鼠标的坐标
        x, y = self.position  # 将坐标元组赋值给x和y
        w, h = self.image_up.get_size()  # 获取正常按钮的宽度和高度
        in_x = x < point_x < x + w  # 定义鼠标移动到按钮上以后的x坐标
        in_y = y < point_y < y + h  # 定义鼠标移动到按钮上以后的y坐标
        return in_x and in_y  # 返回in_x和in_y的值

    def render(self):  # 将按钮动画绘制到屏幕上
        x, y = self.position
        w, h = self.image_up.get_size()

        if self.is_over():
            screen.blit(self.image_down, (x - w / 2, y - h / 2))
        else:
            screen.blit(self.image_up, (x - w / 2, y - h / 2))

    def is_start(self):  # 设置开始后的效果
        if self.is_over():
            b1, b2, b3 = pygame.mouse.get_pressed()
            if b1 == 1:
                self.game_start = True


# 重置子弹函数
def reset_arrow():
    arrow.rect.y = random.randint(340, 420)  # 子弹的y坐标等于340至420的随机数
    arrow.rect.x = 800  # 子弹的x坐标等于800


# 音乐效果函数，用来播放爆炸音效
def explosion_sound():
    ex_sound = pygame.mixer.Sound("audio/exlposion.wav")  # 读取爆炸声音效果文件
    channel = pygame.mixer.find_channel(True)  # 定义一个播放频道
    channel.play(ex_sound)  # 加载爆炸声音


# 音乐效果函数，用来播放eason中弹后的叫声
def eason_sound():
    eason_sound = pygame.mixer.Sound("audio/bullet.wav")  # 读取爆炸声音效果文件
    channel = pygame.mixer.find_channel(True)  # 定义一个播放频道
    channel.set_volume(2)  # 设置音量为2
    channel.play(eason_sound)  # 加载爆炸声音


# 定义输出文字的函数
def print_word(font, x, y, text, color=(255, 255, 255)):
    # 定义5个参数：字体，显示到屏幕的x、y轴坐标、文本的内容、文本的颜色
    img_word = font.render(text, True, color)  # 将文本的内容、颜色存储到img_word中
    screen.blit(img_word, (x, y))  # 绘制文本到屏幕


# 主程序部分
pygame.init()  # 游戏初始化
screen = pygame.display.set_mode((640, 480))  # 设置游戏窗口大小
pygame.display.set_caption("Eason快跑")  # 设置游戏窗口标题
background = pygame.image.load("image/bg_sky.png")  # 读取游戏的背景到background变量中
interface = pygame.image.load("image/interface.png")  # 游戏初始页面的背景

# 创建精灵组
sprite_group = pygame.sprite.Group()  # 创建角色精灵组
exp_group = pygame.sprite.Group()  # 创建爆炸效果精灵组

# 创建eason精灵
eason = MySprite("image/eason.png", 4, 320, 350)
# eason继承精灵类，四个参数分别是精灵图片、精灵图片中精灵个数、x、y坐标值
sprite_group.add(eason)  # 精灵组中添加eason

# 创建怪兽精灵
dragon = MySprite("image/dragon.png", 4, 100, 320)
# 怪兽继承精灵类，四个参数分别是精灵图片、精灵图片中精灵个数、x、y坐标值
sprite_group.add(dragon)  # 精灵组中添加怪兽

# 创建子弹精灵
arrow = MySprite("image/arrow.png", 1, 500, 370)
# 子弹继承精灵类，四个参数分别是精灵图片、精灵图片中精灵个数、x、y坐标值
sprite_group.add(arrow)  # 精灵组中添加子弹

# 创建爆炸动画
explosion = Explosion("image/explosion.png", 4, 4, 320, 350)
# 爆炸精灵继承爆炸类，五个参数分别是：爆炸图片、图片中每行精灵个数、图片中精灵行数、x、y坐标值

# 创建按钮
button = Button("image/button1.png", "image/button2.png", (320, 450))
# 按钮继承按钮类，三个参数分别是按钮正常的图片、按钮按下的图片、按钮的坐标元组

# 时钟和刷新率
fcc_clock = pygame.time.Clock()  # 获取pygame的时钟
fps = 30  # 设置刷新率，数字越大越高。

# 定义需要的变量
arrow_vel = 10  # 被子弹击中后，后退的距离
player_jumping = False  # 定义一个玩家跳跃变量，初始为否，没跳跃。
jump_vel = 0.0  # 定义跳跃的初始数值。
eason_start_y = eason.rect.y  # eason初始y坐标
game_over = False  # 设置一个游戏结束的开关，默认是否

# 创建地图对象
bg1 = RunMap(0, 0)  # 创建一个地图1的变量继承RunMap类，坐标为0,0
bg2 = RunMap(1000, 0)  # 比实际地图大小要小一点，否则会出现缝隙。

# 正确添加背景音乐的代码，可以播放mp3文件，而且循环播放
pygame.mixer.init()  # 音乐初始化
pygame.mixer_music.load("audio/bg_music.ogg")  # 读取音乐文件
pygame.mixer_music.play(-1, 0)
'''播放音乐流，里面两个参数，第一个是循环的次数，如果
为-1则无限循环播放；第二个参数是从哪里开始播放，0就是从最开始播放'''


while True:  # 进入游戏主循环
    for event in pygame.event.get():
        # 使用event.get()方法只能检测按键单次动作，如果希望按下按键有持续动作，需要key.get_pressed()方法
        if event.type == pygame.QUIT:  # 如果监听到键盘的动作等于pygame的退出
            sys.exit()  # 退出游戏窗口

    keys = pygame.key.get_pressed()  # 该方法能检测键盘持续按下时的动作
    '''
    这段代码是控制eason上下左右移动的，可用于坦克大战、飞机大战等游戏
    if keys[K_RIGHT]:
        eason.rect.x += 10
    elif keys[K_LEFT]:
        eason.rect.x -= 10
    elif keys[K_UP]:
        eason.rect.y -= 10
    elif keys[K_DOWN]:
        eason.rect.y += 10
        '''
    if keys[K_SPACE]:  # 如果按键的值是空格
        if not player_jumping:  # 如果游戏角色没跳跃
            player_jumping = True  # 跳跃开关打开
            jump_vel = -12.0  # 设置跳跃值为12像素

    screen.blit(interface, (0, 0))  # 绘制游戏主页面的背景
    button.render()  # 绘制按钮
    button.is_start()  # 调用按钮开始效果

    if button.game_start == True:  # 如果点击按钮开始游戏

        if not game_over:  # 如果游戏没有结束，游戏结束开关为否

            arrow.rect.x -= arrow_vel  # 每次循环子弹往后移动10像素
            if arrow.rect.x < -40:  # 如果子弹的x坐标小于-40
                reset_arrow()  # 运行重置子弹函数

        # 检测玩家是否跳跃，这段代码形成游戏角色不断上升或不断下降的动画效果
        if player_jumping:
            if jump_vel < 0:
                jump_vel += 0.6
            elif jump_vel >= 0:
                jump_vel += 0.8
            eason.rect.y += jump_vel
            if eason.rect.y > eason_start_y:
                player_jumping = False
                eason.rect.y = eason_start_y
                jump_vel = 0.0

        # 检测子弹是否击中eason
        if pygame.sprite.collide_rect(arrow, eason):  # 如果子弹碰到eason
            reset_arrow()  # 重置子弹位置
            explosion.rect.x = eason.rect.x + 30
            # 爆炸效果的y坐标等于eason的x坐标加30像素，这样做更像子弹在eason前面爆炸而不是在身体里
            explosion.rect.y = eason.rect.y  # 爆炸效果的y坐标等于eason的y坐标
            explosion.explosion_switch = True  # 爆炸效果的开关打开
            exp_group.add(explosion)  # 精灵组加入爆炸效果
            eason.rect.x -= 10  # eason往左移动10像素，形成被击中后退的效果
            explosion_sound()  # 播放爆炸声音效果
            eason_sound()  # 播放eason受伤声音效果

        # 检测子弹是否击中怪物
        if pygame.sprite.collide_rect(arrow, dragon):  # 如果子弹碰到怪兽
            reset_arrow()  # 重置子弹位置
            explosion.rect.x = dragon.rect.x + 60
            # 爆炸效果的y坐标等于eason的x坐标加60像素，这样做更像子弹在怪兽前面爆炸而不是在身体里
            explosion.rect.y = dragon.rect.y  # 爆炸效果的y坐标等于怪兽的y坐标
            explosion.explosion_switch = True  # 爆炸效果的开关打开
            exp_group.add(explosion)  # 精灵组加入爆炸效果
            dragon.rect.x -= 10  # 怪兽往左移动10像素，形成被击中后退的效果
            explosion_sound()  # 播放eason受伤声音效果

        # 检测eason是否被怪物追上
        if pygame.sprite.collide_rect(eason, dragon):  # 如果eason碰到了怪物
            game_over = True  # 游戏结束开关开

        screen.blit(background, (0, 0))  # 绘制背景

        bg1.map_update()  # 更新地图
        bg2.map_update()  # 更新地图2
        bg1.map_rolling()  # 滚动地图
        bg2.map_rolling()  # 滚动地图2

        if not game_over:  # 如果游戏结束开关为否，游戏没结束
            sprite_group.update()  # 更新精灵组
            exp_group.update()  # 更新爆炸组
        sprite_group.draw(screen)  # 将精灵组绘制到屏幕上
        exp_group.draw(screen)  # 将爆炸组绘制到屏幕上

        if game_over:  # 如果游戏结束开关为是，游戏结束了
            screen.fill((200, 200, 200))  # 绘制一个灰色背景
            print_word(pygame.font.Font(None, 64), 180, 200, "GAME OVER !")
            # 调用print_word函数输出“GAME OVER !”字样

    fcc_clock.tick(fps)  # 设置游戏窗口刷新率为fps的值
    pygame.display.flip()  # 绘制窗口
